Echo Server

 

Deadline: 31/3/2011 at 16:00

 

General Instructions:

 

This exercise should be carried in groups of three. You have until Friday, March 11 to email Sotirios Terzis the group you are going to work in. Anybody that fails to do so will be allocated into a group by the teaching team. Note that you are not allowed to work for this exercise with anyone that you worked with in the first one.

 

The exercise consists of 7 parts, with each part having its own weight (see below). The above submission deadline is strict (i.e. no late submissions will be accepted).

 

For the submission of your exercise you should produce a single zip file that includes:

The zip file should be submitted using Spider.

 

For marking you will have to demonstrate your solution working in the lab to the lecturer or the postgraduate class demonstrators. Details about these demonstrations will follow in due time.

 

Exercise Description:

 

An echo server is a server that echoes back whatever it receives from a client. For example, if a client sends the server the string Hello there! the server will respond with the exact data it received from the client—that is, Hello there!

 

Part A

Write an echo server using the Java networking API. This server will wait for a client connection using the accept() method. When a client connection is received, the server will loop, performing the following steps:

The server will break out of the loop only when it has determined that the client has closed the connection.

The date server shown in the lectures uses the java.io.BufferedReader class. BufferedReader extends the java.io.Reader class, which is used for reading character streams. However, the echo server cannot guarantee that it will read characters from clients; it may receive binary data as well. The class java.io.InputStream deals with data at the byte level rather than the character level. Thus, this echo server must use an object that extends java.io.InputStream. The read() method in the java.io.InputStream class returns 1 when the client has closed its end of the socket connection.

To test your server implementation, develop a client that connects to the server, reads input typed in by the user, until the user indicates with a special character it wishes to exit, sends the user input to the server, and writes the server’s responses into a file.

 

Part B

The above server is single-threaded, meaning the server cannot respond to concurrent echo clients until the current client exits. Modify your implementation so that the echo server services each client in a separate thread.

 

Part C

Modify your part B implementation so that each client is serviced using a thread pool. Experiment with the three different models of thread-pool architecture in order to determine how the behaviour of the server changes for each of them.

 

Part D

Servers can be designed to limit the number of open connections. For example, a server may wish to have only N socket connections at any point in time. As soon as N connections are made, the server will not accept another incoming connection until an existing connection is released. Modify your part B implementation to use Java semaphores so that the number of concurrent connections is limited.

 

Part E

Create a thread pool using Java synchronization. Your thread pool will implement the following API:

ThreadPool()–Create a default-sized thread pool.

ThreadPool(int size)–Create a thread pool of size size.

void add(Runnable task)–Add a task to be performed by a thread in the pool.

void stopPool()–Stop all threads in the pool.

Your pool will first create a number of idle threads that await work. Work will be submitted to the pool via the add() method, which adds a task implementing the Runnable interface. The add() method will place the Runnable task into a queue. Once a thread in the pool becomes available for work, it will check the queue for any Runnable tasks. If there are such tasks, the idle thread will remove the task from the queue and invoke its run() method. If the queue is empty, the idle thread will wait to be notified when work becomes available. (The add() method will perform a notify() when it places a Runnable task into the queue to possibly awaken an idle thread awaiting work.) The stopPool() method will stop all threads in the pool by invoking their interrupt() method. This of course requires that Runnable tasks being executed by the thread pool check their interruption status.

Test your solution to this problem by modifying your part B implementation so that it now uses your thread pool.

 

Part F

Modify your part C implementation so that the serve keeps a count of the number the client connections it receives and the number of total number of messages echoed. In this implementation each echoed message start with the sequence number of the connection and the sequence number of the echoed message, e.g. clients is the 5th connection and sends the overall 10th message to be “Hi!”, the server will respond “5.10: Hi!”. Utilise the Java built-in thread synchronisation facilities to ensure that there are no race conditions in your code.

 

Part G
Modify your part F implementation so that instead of the Java built-in synchronisation facilities, it uses locks and condition variables to produce exactly the same behaviour.

 

Marking Scheme:

 

Each of the above parts is marked as follows: